/*
 * Jamus
 * Copyright (C) 2010 Jamus Team
 *
 * Jamus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jamus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jamus.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <iostream>
#include <boost/assign.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>

#include "Plugin.h"
#include "Connection.h"
#include "IllegalCommandException.h"
#include "IllegalArgumentException.h"
#include "CloseConnectionException.h"
#include "DoNotReplyException.h"
#include "../../NoCurrentTrackException.h"

using namespace std;
using namespace boost::asio;

namespace frontend {
namespace mpd {

log4cxx::LoggerPtr Connection::logger(log4cxx::Logger::getLogger("frontend.mpd.Connection"));

// Initialize handler map
map<string, Connection::HandlerPtr> Connection::handlers = boost::assign::map_list_of
	("add", &Connection::add)
	("clear", &Connection::clear)
	("close", &Connection::close)
	("commands", &Connection::commands)
	("currentsong", &Connection::currentsong)
	("delete", &Connection::delete_)
	("find", &Connection::find)
	("idle", &Connection::idle)
	("list", &Connection::list)
	("listplaylistinfo", &Connection::listplaylistinfo)
	("lsinfo", &Connection::lsinfo)
	("next", &Connection::next)
	("noidle", &Connection::noidle)
	("notcommands", &Connection::notcommands)
	("outputs", &Connection::outputs)
	("pause", &Connection::pause)
	("play", &Connection::play)
	("playid", &Connection::playid)
	("playlistid", &Connection::playlistid)
	("playlistinfo", &Connection::playlistinfo)
	("plchanges", &Connection::plchanges)
	("previous", &Connection::previous)
	("seek", &Connection::seek)
	("seekid", &Connection::seekid)
	("setvol", &Connection::setvol)
	("stats", &Connection::stats)
	("status", &Connection::status)
	("stop", &Connection::stop)
	("tagtypes", &Connection::tagtypes)
	;

Connection::Connection(io_service& ios, backend::Plugin& backend, frontend::mpd::Plugin& frontend)
: frontend(frontend),
  backend(backend),
  socket(ios),
  retryIfAborted(false),
  strand(ios),
  commandListActive(false),
  isIdle(false),
  idleEvents(0)
{
	LOG4CXX_TRACE(logger, "Create Connection " << this);
}

Connection::~Connection()
{
	LOG4CXX_TRACE(logger, "Destroy Connection " << this);
}

void Connection::start()
{
	LOG4CXX_TRACE(logger, "Start Connection " << this << " to "
			<< socket.remote_endpoint());

	// Say hello
	writeBuf = "OK MPD 0.15.0\n";
	write();

	// Connect to back-end signals so we are able to notify our clients of
	// changes
	backend.connectPlaylistChanged(
			backend::Plugin::PlaylistChangedSlot(
				&Connection::onPlaylistChanged,
				this).track(shared_from_this()));

	backend.connectPlaybackStatusChanged(
			backend::Plugin::PlaybackStatusChangedSlot(
				&Connection::onPlaybackStatusChanged,
				this).track(shared_from_this()));

	backend.connectVolumeChanged(
			backend::Plugin::VolumeChangedSlot(
				&Connection::onVolumeChanged,
				this).track(shared_from_this()));
}

void Connection::onPlaylistChanged()
{
	boost::lock_guard<boost::mutex> lock(mutex);

	LOG4CXX_TRACE(logger, "Got onPlaylistChanged signal -- isIdle: " << isIdle << " -- idleEvents (before): " << idleEvents);
	idleEvents |= CHANGED_PLAYLIST;
	notifyIfIdle();
}

void Connection::onPlaybackStatusChanged()
{
	boost::lock_guard<boost::mutex> lock(mutex);

	LOG4CXX_TRACE(logger, "Got onPlaybackStatusChanged signal -- isIdle: " << isIdle << " -- idleEvents (before): " << idleEvents);
	idleEvents |= CHANGED_PLAYBACK_STATUS;
	notifyIfIdle();
}

void Connection::onVolumeChanged()
{
	boost::lock_guard<boost::mutex> lock(mutex);

	LOG4CXX_TRACE(logger, "Got onVolumeChanged signal -- isIdle: " << isIdle << " -- idleEvents (before): " << idleEvents);
	idleEvents |= CHANGED_MIXER;
	notifyIfIdle();
}

void Connection::read()
{
	async_read_until(
			socket,
			readBuf,
			'\n',
			strand.wrap(boost::bind(
					&Connection::onRead,
					shared_from_this(),
					boost::asio::placeholders::error,
					boost::asio::placeholders::bytes_transferred)));
}

void Connection::write()
{
	async_write(
			socket,
			buffer(writeBuf),
			strand.wrap(boost::bind(
					&Connection::onWrite,
					shared_from_this(),
					boost::asio::placeholders::error,
					boost::asio::placeholders::bytes_transferred)));
}

void Connection::onWrite(const boost::system::error_code& error, std::size_t n)
{
	boost::lock_guard<boost::mutex> lock(mutex);

	if(error) {
		if(retryIfAborted) {
			// Wait until status update operation is finished
			retryIfAborted = false;
			write();
		}
		return;
	}

	// Wait for next command
	read();
}

void Connection::onRead(const boost::system::error_code& error, std::size_t n)
{
	boost::lock_guard<boost::mutex> lock(mutex);

	if(error) {
		if(retryIfAborted) {
			// Wait until status update operation is finished
			retryIfAborted = false;
			read();
		}
		return;
	}

	istream is(&readBuf);
	string line;
	getline(is, line);
	boost::trim(line);

	LOG4CXX_DEBUG(logger, "Got request: " << line);

	if(!commandListActive && line == "command_list_begin") {
		commandListActive = true;
		read();
		return;
	}

	// Add this command to the command list
	if(!commandListActive || line != "command_list_end")
		commandList.push_back(line);

	if(!commandListActive || line == "command_list_end") {
		// Commit command list
		ostringstream response;

		size_t numDoNotReply = 0; // Counts how many commands in the list should NOT have a reply sent. Used to decide whether an OK should be appended.
		size_t currentCommand;
		string currentCommandName;

		try {
			//BOOST_FOREACH(string command, commandList) {
			for(currentCommand = 0; currentCommand < commandList.size(); ++currentCommand) {
				const string& request = commandList[currentCommand];

				istringstream requestStream(request);
				requestStream >> currentCommandName;

				try {
					response << handleRequest(request);
				} catch(DoNotReplyException&) {
					++numDoNotReply;
				}
			}
		} catch(IllegalCommandException& e) {
			response << "ACK [5@" << currentCommand << "] {} unknown command: \"" << e.what() << "\"\n";
			numDoNotReply = commandList.size(); // only so that no OK is appended
		} catch(IllegalArgumentException& e) {
			response << "ACK [2@" << currentCommand << "] {" << currentCommandName << "} illegal argument: " << e.what() << '\n';
			numDoNotReply = commandList.size(); // only so that no OK is appended
		} catch(CloseConnectionException&) {
			socket.close();
			return;
		} catch(std::runtime_error& e) {
			// XXX: Probably not the right error code
			response << "ACK [52@" << currentCommand << "] {" << currentCommandName << "} " << e.what() << '\n';
			numDoNotReply = commandList.size(); // only so that no OK is appended
		}

		// Append OK (if a response should be sent)
		if(numDoNotReply < commandList.size())
			response << "OK\n";

		writeBuf = response.str();

		commandList.clear();
		commandListActive = false;

		LOG4CXX_TRACE(logger, "Response: " << response.str());

		if(writeBuf.empty()) // Don't reply. Wait for next command
			read();
		else // Send response
			write();
	}
	else // Command list is active. This command was added to the list. Read the next one.
		read();
}

string Connection::trackInfo(const Track& track) const {
	ostringstream ss;

	ss << "file: " << track.getFileName() << '\n'
		<< "Time: " << track.getDuration().total_seconds() << '\n';

	if(!track.getArtist().empty())
		ss << "Artist: " << track.getArtist() << '\n';

	if(!track.getTitle().empty())
		ss << "Title: " << track.getTitle() << '\n';

	if(!track.getAlbum().empty())
		ss << "Album: " << track.getAlbum() << '\n';

	if(track.getTrackNumber())
		ss << "Track: " << track.getTrackNumber() << '\n';

	return ss.str();
}

string Connection::trackInPlaylistInfo(const backend::Plugin::Playlist& playlist, int index) const {
	ostringstream ss;

	if(index < 0 || static_cast<size_t>(index) >= playlist.size())
		throw runtime_error("Illegal track number specified");

	ss << trackInfo(playlist[index]);
	ss << "Pos: " << index << '\n';
	ss << "Id: " << frontend.getTrackIdFromPlaylistIndex(index) << '\n';
	return ss.str();
}

string Connection::endIdle()
{
	LOG4CXX_DEBUG(logger, "endIdle called; idleEvents: " << idleEvents);

	assert(isIdle);
	isIdle = false;

	ostringstream response;

	if(idleEvents & CHANGED_PLAYLIST)
		response << "changed: playlist\n";

	if(idleEvents & CHANGED_PLAYBACK_STATUS)
		response << "changed: player\n";

	if(idleEvents & CHANGED_MIXER)
		response << "changed: mixer\n";

	idleEvents = 0;

	return response.str();
}

void Connection::notifyIfIdle()
{
	// Precondition: mutex is locked

	if(isIdle) {
		retryIfAborted = true;

		socket.cancel(); // causes async handlers to receive error code
		// Since we own the mutex, the async handlers wait until we release it before retrying their request

		string statusUpdateWriteBuf = endIdle() + "OK\n";
		boost::asio::write(socket, buffer(statusUpdateWriteBuf));
	}
}

string Connection::handleRequest(const string& request)
{
	// First word of request is command name
	istringstream requestStream(request);
	string command;
	requestStream >> command;
	boost::to_lower(command);

	if(isIdle && command != "noidle")
		throw IllegalCommandException(command);

	HandlerMap::const_iterator handler = handlers.find(command);
	if(handler == handlers.end())
		throw IllegalCommandException(command);

	// Pass argument(s) to the handler
	// Skip whitespace
	requestStream >> ws;
	// Get rest of the line
	string argStr;
	getline(requestStream, argStr);
	boost::trim(argStr);

	// Split into arguments
	vector<string> args;
	boost::tokenizer<boost::escaped_list_separator<char> > tokenizer(
			argStr,
			boost::escaped_list_separator<char>('\\', ' ', '"'));
	BOOST_FOREACH(string s, tokenizer)
		args.push_back(s);

	return (this->*(handler->second))(args);
}

// ****************************************************************************
// Request handlers
// ****************************************************************************

string Connection::add(const HandlerArgs& args)
{
	if(args.size() != 1)
		throw IllegalArgumentException("add expects 1 parameter");

	backend.addToPlaylist(args[0]);

	return string();
}

string Connection::clear(const HandlerArgs& args)
{
	backend.clearPlaylist();

	return string();
}

string Connection::close(const HandlerArgs& args)
{
	throw CloseConnectionException();
}

string Connection::commands(const HandlerArgs& args)
{
	ostringstream oss;

	typedef pair<string, HandlerPtr> pair_t;
	BOOST_FOREACH(pair_t p, handlers) {
		if(boost::starts_with(p.first, "command_list"))
			continue; // MPD doesn't show those

		oss << "command: " << p.first << '\n';
	}

	return oss.str();
}

string Connection::currentsong(const HandlerArgs& args)
{
	int index;

	try {
		index = backend.getCurrentTrack();
	} catch(NoCurrentTrackException&) {
		return string();
	}

	assert(index >= 0 && (size_t)index < backend.getPlaylist().size());

	ostringstream info;
	info << trackInPlaylistInfo(backend.getPlaylist(), index);
	return info.str();
}

string Connection::delete_(const HandlerArgs& args)
{
	if(args.size() != 1)
		throw IllegalArgumentException("delete expects 1 parameter");

	backend.removeFromPlaylist(boost::lexical_cast<int>(args[0]));

	return string();
}

string Connection::find(const HandlerArgs& args)
{
	// find {TYPE} {WHAT}
	// Finds songs in the db that are exactly WHAT. TYPE should be album, artist, or title. WHAT  is what to find.
	// What is not said in the doc is that the sequence <type, what> can be repeated.
	// In the MPD source, TYPE can be any tag.
	
	if(args.size() < 2 || args.size() % 2 != 0)
		throw IllegalArgumentException("find expects 2*n (n>0) parameters");

	ostringstream response;

	const backend::Plugin::TrackList& tracks = backend.getTrackList();

	BOOST_FOREACH(Track track, tracks) {
		bool matches = true;

		for(size_t i = 0; i < args.size();) {
			const string& type = args[i++];
			const string& what = args[i++];

			const string* actual;

			if(boost::iequals(type, "album"))
				actual = &track.getAlbum();
			else if(boost::iequals(type, "artist"))
				actual = &track.getArtist();
			else if(boost::iequals(type, "title"))
				actual = &track.getTitle();
			else
				throw IllegalArgumentException("Unexpected tag name for 'type'");

			if(*actual != what) {
				matches = false;
				break;
			}
		}

		if(matches)
			response << trackInfo(track);
	}

	return response.str();
}

string Connection::idle(const HandlerArgs& args)
{
	assert(!isIdle);
	LOG4CXX_DEBUG(logger, "Enter idle mode");
	isIdle = true;

	if(idleEvents)
		return endIdle();

	throw DoNotReplyException();
}

string Connection::list(const HandlerArgs& args)
{
	// Documentation says:
	// list {TYPE} [ARTIST]
	// TYPE: album | artist.
	// Apparently (according to the MPD source), this is old behavior.

	// The way MPD implements "list":
	// list {TAG} ({TYPE} {WHAT})*
	// TAG: name of any tag (artist, album, title, composer, ...)
	// TYPE: likewise
	// WHAT: value of TYPE
	// Lists which distinct values for TAG are in the DB for tracks that satisfy TYPE = WHAT

	// In the following, implement the latter
	ostringstream response;

	if(args.size() % 2 != 1)
		throw IllegalArgumentException("list expects 1 + 2*n parameters (n >= 0)");

	std::list<Track> satisfyingTracks;

	BOOST_FOREACH(Track track, backend.getTrackList()) {
		// Does this track satisfy all conditionals?
		bool matches = true;
		
		for(size_t i = 1; i < args.size(); i += 2) {
			if(!track.matches(args[i], args[i+1])) {
				matches = false;
				break;
			}
		}

		if(matches)
			satisfyingTracks.push_back(track);
	}

	// Some clients send "list artist" but rely on a response like "Artist: a\nArtist: b\n..."
	// I. e. the capital A matters to some :(
	string type;
	if(boost::iequals(args[0], "file") || boost::iequals(args[0], "filename"))
		type = "file";
	else if(boost::iequals(args[0], "artist"))
		type = "Artist";
	else if(boost::iequals(args[0], "album"))
		type = "Album";
	else if(boost::iequals(args[0], "title"))
		type = "Title";
	else if(boost::iequals(args[0], "track"))
		type = "Track";
	else if(boost::iequals(args[0], "date"))
		type = "Date";
	else {
		LOG4CXX_WARN(logger, "Handler for list command got unknown type '" << args[0] << "' -- returning empty list");
		return string();
	}

	set<string> values;
	BOOST_FOREACH(Track track, satisfyingTracks) {
		if(type == "file")
			values.insert(track.getFileName());
		else if(type == "Artist")
			values.insert(track.getArtist());
		else if(type == "Album")
			values.insert(track.getAlbum());
		else if(type == "Title")
			values.insert(track.getTitle());
		else if(type == "Track")
			values.insert(boost::lexical_cast<string>(track.getTrackNumber()));
		else if(type == "Date")
			values.insert("2010"); // TODO
		else
			assert(false);
	}

	BOOST_FOREACH(string v, values)
		response << type << ": " << v << '\n';

	return response.str();
}

string Connection::listplaylistinfo(const HandlerArgs& args)
{
	return string(); // TODO
}

string Connection::lsinfo(const HandlerArgs& args)
{
	return string(); // TODO
}

string Connection::outputs(const HandlerArgs& args)
{
	return "outputid: 0\noutputname: Jamus Dummy\noutputenabled: 1\n"; // TODO
}

string Connection::next(const HandlerArgs& args)
{
	backend.nextTrack();
	return string();
}

string Connection::noidle(const HandlerArgs& args)
{
	if(!isIdle)
		throw DoNotReplyException();

	return endIdle();
}

string Connection::notcommands(const HandlerArgs& args)
{
	return string(); // TODO
}

string Connection::pause(const HandlerArgs& args)
{
	backend.pause();
	return string();
}

string Connection::play(const HandlerArgs& args)
{
	// Optional argument: Playlist index
	// cf. playid
	if(args.empty())
		backend.play();
	else if(args.size() == 1)
		backend.play(boost::lexical_cast<int>(args[0]));
	else
		throw IllegalArgumentException("play expects 0 or 1 parameters");

	return string();
}

string Connection::playid(const HandlerArgs& args)
{
	// Optional argument: Track ID
	// cf. play
	if(args.empty())
		backend.play();
	else if(args.size() == 1)
		backend.play(frontend.getTrackIdFromPlaylistIndex(boost::lexical_cast<int>(args[0])));
	else
		throw IllegalArgumentException("playid expects 0 or 1 parameters");

	return string();
}

string Connection::playlistid(const HandlerArgs& args)
{
	// Optional argument: Track ID
	// cf. playlistinfo
	if(args.empty()) {
		// Print information about all tracks in the playlist
		return playlistinfo(args);
	}
	else if(args.size() == 1) {
		// Print information about the track with the specified ID
		return trackInPlaylistInfo(backend.getPlaylist(), frontend.getPlaylistIndexFromTrackId(boost::lexical_cast<int>(args[0])));
	}
	else
		throw IllegalArgumentException("playlistid expects 0 or 1 parameters");
}

string Connection::playlistinfo(const HandlerArgs& args)
{
	// Optional argument: Playlist index
	// cf. playlistid
	if(args.empty()) {
		// Print information about all tracks in the playlist
		ostringstream response;
		const backend::Plugin::Playlist& pl = backend.getPlaylist();

		for(size_t i = 0; i < pl.size(); ++i)
			response << trackInPlaylistInfo(pl, i);

		return response.str();
	}
	else if(args.size() == 1) {
		// Print information about the track with the specified index
		return trackInPlaylistInfo(backend.getPlaylist(), boost::lexical_cast<int>(args[0]));
	}
	else
		throw IllegalArgumentException("playlistinfo expects 0 or 1 parameters");
}

string Connection::plchanges(const HandlerArgs& args)
{
	// Let's always respond with the whole playlist for now.
	// FIXME: This is wrong.
	
	ostringstream response;

	const backend::Plugin::Playlist& pl = backend.getPlaylist();

	for(size_t pos = 0; pos < pl.size(); ++pos)
		response << trackInPlaylistInfo(pl, pos);

	return response.str();
}

string Connection::previous(const HandlerArgs& args)
{
	backend.prevTrack();
	return string();
}

string Connection::setvol(const HandlerArgs& args)
{
	if(args.size() != 1)
		throw IllegalArgumentException("setvol expects 1 parameter");

	int vol = boost::lexical_cast<int>(args[0]);

	if(vol < 0 || vol > 100)
		throw IllegalArgumentException("Volume must be between 0 and 100");

	backend.setVolume(vol);

	return string();
}

string Connection::seek(const HandlerArgs& args)
{
	if(args.size() != 2)
		throw IllegalArgumentException("seek expects 2 parameters");

	const int index = boost::lexical_cast<int>(args[0]);

	if(index < 0)
		throw IllegalArgumentException("seek index cannot be less than 0");

	const int pos = boost::lexical_cast<int>(args[1]);

	if(pos < 0)
		throw IllegalArgumentException("seek position cannot be less than 0");

	// If index is not the current track, change track first
	int currentTrack;
	try {
		currentTrack = backend.getCurrentTrack();
	} catch(NoCurrentTrackException&) {
		currentTrack = -1;
	}

	if(index != currentTrack) {
		LOG4CXX_TRACE(logger, "Seek on another song -- playing pos=" << index);
		backend.play(index);
		// XXX: We need to wait until the backend has noticed that the current track changed. This is no solution.
		boost::this_thread::sleep(boost::posix_time::milliseconds(300));
	}

	backend.seek(boost::posix_time::seconds(pos));

	return string();
}

string Connection::seekid(const HandlerArgs& args)
{
	if(args.size() != 2)
		throw IllegalArgumentException("seekid expects 2 parameters");

	const int id = boost::lexical_cast<int>(args[0]);

	if(id < 0)
		throw IllegalArgumentException("seek id cannot be less than 0");

	const int pos = boost::lexical_cast<int>(args[1]);

	if(pos < 0)
		throw IllegalArgumentException("seek position cannot be less than 0");

	// If id is not the current track, change track first
	int currentTrackId;
	try {
		currentTrackId = frontend.getTrackIdFromPlaylistIndex(backend.getCurrentTrack());
	} catch(NoCurrentTrackException&) {
		currentTrackId = -1;
	}

	if(id != currentTrackId) {
		LOG4CXX_TRACE(logger, "Seek on another song -- playing pos=" << frontend.getPlaylistIndexFromTrackId(id) << ", id=" << id);
		backend.play(frontend.getPlaylistIndexFromTrackId(id));
		// XXX: We need to wait until the backend has noticed that the current track changed. This is no solution.
		boost::this_thread::sleep(boost::posix_time::milliseconds(300));
	}

	backend.seek(boost::posix_time::seconds(pos));

	return string();
}

string Connection::stats(const HandlerArgs& args)
{
	ostringstream response;
	
	// XXX: This is just a quick and dirty hack
	set<string> artists, albums;
	boost::posix_time::time_duration playtime(0,0,0,0);

	BOOST_FOREACH(Track t, backend.getTrackList()) {
		artists.insert(t.getArtist());
		albums.insert(t.getAlbum());
		playtime += t.getDuration();
	}

	response << "artists: " << artists.size() << '\n'
		<< "albums: " << albums.size() << '\n'
		<< "songs: " << backend.getTrackList().size() << '\n'
		<< "uptime: 10694\n" // TODO
		<< "playtime: 258\n" // TODO
		<< "db_playtime: " << playtime.seconds() << '\n'
		<< "db_update: 1260796974\n"; // TODO

	return response.str();
}

string Connection::status(const HandlerArgs& args)
{
	ostringstream response;

	const backend::Plugin::Playlist& playlist = backend.getPlaylist();

	response << "volume: " << backend.getVolume() << '\n'
		<< "repeat: 0\n"
		<< "random: 0\n"
		<< "single: 0\n"
		<< "consume: 0\n"
		<< "playlist: " << frontend.getPlaylistVersion() << '\n'
		<< "playlistlength: " << playlist.size() << '\n'
		<< "xfade: 0\n";

	// Print playback status
	backend::Plugin::PlaybackStatus playbackStatus = backend.getPlaybackStatus();
	switch(playbackStatus) {
		case backend::Plugin::STOP:
			response << "state: stop\n";
			break;
		case backend::Plugin::PLAY:
			response << "state: play\n";
			break;
		case backend::Plugin::PAUSE:
			response << "state: pause\n";
			break;
	}

	// Print info about current song
	try {
		int currentTrackIndex = backend.getCurrentTrack();
		response << "song: " << currentTrackIndex << '\n';
		response << "songid: " << frontend.getTrackIdFromPlaylistIndex(currentTrackIndex) << '\n';

		// If on pause or play, print more about current song
		if(playbackStatus == backend::Plugin::PLAY || playbackStatus == backend::Plugin::PAUSE) {
			const Track& currentTrack = playlist.at(currentTrackIndex);

			// TODO: MPD >= 0.16 also prints 'elapsed' with milliseconds granularity. But since I'm currently using (and implementing) MPD 0.15...
			response << "time: " << backend.getElapsed().total_seconds() << ':' << currentTrack.getDuration().total_seconds() << '\n'
				<< "bitrate: 128\n" // TODO
				<< "audio: 44100:24:2\n"; // TODO
		}

		// TODO: nextsong, nextsongid
	} catch(NoCurrentTrackException&) {}

	return response.str();
}

string Connection::stop(const HandlerArgs& args)
{
	backend.stop();
	return string();
}

string Connection::tagtypes(const HandlerArgs& args)
{
	// This must be kept up to date with the Track interface
	ostringstream response;
	response << "tagtype: Artist\n"
		<< "tagtype: Album\n"
		<< "tagtype: Title\n"
		<< "tagtype: Track\n";
	return response.str();
}

} // namespace mpd
} // namespace frontend
